/**
 *  Copyright (c) 2018 Angelo ZERR
 *  All rights reserved. This program and the accompanying materials
 *  are made available under the terms of the Eclipse Public License v2.0
 *  which accompanies this distribution, and is available at
 *  http://www.eclipse.org/legal/epl-v20.html
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 *  Contributors:
 *  Angelo Zerr <angelo.zerr@gmail.com> - initial API and implementation
 */
package org.eclipse.lemminx.uriresolver;

import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.apache.xerces.impl.XMLEntityManager;
import org.apache.xerces.util.URI.MalformedURIException;
import org.apache.xerces.xni.XMLResourceIdentifier;
import org.apache.xerces.xni.XNIException;
import org.apache.xerces.xni.parser.XMLInputSource;

/**
 * URI resolver manager.
 *
 */
public class URIResolverExtensionManager implements URIResolverExtension, IExternalGrammarLocationProvider {

	private final List<URIResolverExtension> resolvers;

	private final URIResolverExtension defaultURIResolverExtension;

	public URIResolverExtensionManager() {
		resolvers = new ArrayList<>();
		this.defaultURIResolverExtension = new DefaultURIResolverExtension();
	}

	class DefaultURIResolverExtension implements URIResolverExtension {

		@Override
		public String getName() {
			return URIResolverExtension.DEFAULT;
		}

		@Override
		public String resolve(String baseLocation, String publicId, String systemId) {
			try {
				return XMLEntityManager.expandSystemId(systemId, baseLocation, false);
			} catch (MalformedURIException e) {
				return systemId;
			}
		}

		@Override
		public XMLInputSource resolveEntity(XMLResourceIdentifier rid) throws XNIException, IOException {
			XMLInputSource is = null;
			String id = rid.getPublicId();
			if (id == null) {
				id = rid.getNamespace();
			}
			String location = null;
			if (id != null || rid.getLiteralSystemId() != null) {
				location = this.resolve(rid.getBaseSystemId(), id, rid.getLiteralSystemId());
			}

			if (location != null) {
				is = new XMLInputSource(rid.getPublicId(), location, location);
			}
			return is;
		}
	}

	/**
	 * Register an URI resolver.
	 *
	 * @param resolver the URI resolver to register.
	 */
	public void registerResolver(URIResolverExtension resolver) {
		resolvers.add(resolver);
	}

	/**
	 * Unregister an URI resolver.
	 *
	 * @param resolver the URI resolver to unregister.
	 */
	public void unregisterResolver(URIResolverExtension resolver) {
		resolvers.add(resolver);
	}

	@Override
	public String resolve(String baseLocation, String publicId, String systemId) {
		for (URIResolverExtension resolver : resolvers) {
			String resolved = resolver.resolve(baseLocation, publicId, systemId);
			if (resolved != null && !resolved.isEmpty()) {
				return resolved;
			}
		}
		return defaultURIResolverExtension.resolve(baseLocation, publicId, systemId);
	}

	public ResolvedURIInfo resolveInfo(String baseLocation, String publicId, String systemId) {
		for (URIResolverExtension resolver : resolvers) {
			String resolvedURI = resolver.resolve(baseLocation, publicId, systemId);
			if (resolvedURI != null && !resolvedURI.isEmpty()) {
				return new ResolvedURIInfo(resolvedURI, resolver);
			}
		}
		String resolvedURI = defaultURIResolverExtension.resolve(baseLocation, publicId, systemId);
		if (resolvedURI != null && !resolvedURI.isEmpty()) {
			return new ResolvedURIInfo(resolvedURI, defaultURIResolverExtension);
		}
		return null;
	}

	@Override
	public XMLInputSource resolveEntity(XMLResourceIdentifier resourceIdentifier) throws XNIException, IOException {
		XMLInputSource is = null;
		for (URIResolverExtension resolver : resolvers) {
			is = resolver.resolveEntity(resourceIdentifier);
			if (is != null) {
				return is;
			}
		}
		return defaultURIResolverExtension.resolveEntity(resourceIdentifier);
	}

	@Override
	public Map<String, String> getExternalGrammarLocation(URI fileURI) {
		for (URIResolverExtension resolver : resolvers) {
			if (resolver instanceof IExternalGrammarLocationProvider) {
				Map<String, String> result = ((IExternalGrammarLocationProvider) resolver)
						.getExternalGrammarLocation(fileURI);
				if (result != null) {
					return result;
				}
			}
		}
		return null;
	}
}
